/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2011 ymnk, JCraft,Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright 
     notice, this list of conditions and the following disclaimer in 
     the documentation and/or other materials provided with the distribution.

  3. The names of the authors may not be used to endorse or promote products
     derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.yisin.ssh2.jsch;

/**
 * Usually not to be used by applications.
 * A chunk of bytes together with methods to access them.
 * This implements the low-level protocol elements used by the
 * different layers of the SSH protocol.
 *<p>
 * The Buffer maintains a current index (from where on the put methods
 * write, updating it on the way) and an {@linkplain #getOffSet offset}.
 * The offset is used by the get methods to read (and they update the
 * offset, too). If a buffer is used for both reading and writing, the
 * index should normally be bigger than the offset. Initially both are 0.
 *</p>
 * @see <a href="http://tools.ietf.org/html/rfc4251#section-5">RFC 4251,
 *  section 5.  Data Type Representations Used in the SSH Protocols</a>
 */
public class Buffer{
  final byte[] tmp=new byte[4];
  byte[] buffer;
  int index;
  int s;

  /**
   * creates a Buffer with given size.
   */
  public Buffer(int size){
    buffer=new byte[size];
    index=0;
    s=0;
  }

  /**
   * Creates a buffer using the given array as backing store.
   */
  public Buffer(byte[] buffer){
    this.buffer=buffer;
    index=0;
    s=0;
  }

  /**
   * Creates a buffer with a default size of 20 KB.
   */
  public Buffer(){ this(1024*10*2); }

  /**
   * puts one byte into the buffer.
   */
  public void putByte(byte foo){
    buffer[index++]=foo;
  }

  /**
   * puts all bytes in the given byte array in the buffer.
   *
   * (This should have been named {@code putBytes}, really.)
   */
  public void putByte(byte[] foo) {
    putByte(foo, 0, foo.length);
  }

  /**
   * puts a subsequence of the given byte array in the buffer.
   *
   * (This should have been named {@code putBytes}, really.)
   */
  public void putByte(byte[] foo, int begin, int length) {
    System.arraycopy(foo, begin, buffer, index, length);
    index+=length;
  }

  /**
   * puts a byte sequence formatted as a SSH string into the buffer.
   * @see #putString(byte[], int, int)
   */
  public void putString(byte[] foo){
    putString(foo, 0, foo.length);
  }
  /**
   * puts a byte sequence formatted as a SSH string into the buffer.
   *
   * A SSH string consists of first a (big-endian) 32-bit number indicating
   * the length of the string (number of bytes), then the bytes themselves.
   *
   * @param foo the array from which we take the data.
   * @param begin the start of the data in the array.
   * @param length how many bytes to put.
   */
  public void putString(byte[] foo, int begin, int length) {
    putInt(length);
    putByte(foo, begin, length);
  }

  /**
   * Puts a 32-bit number as 4 bytes into the buffer.
   */
  public void putInt(int val) {
    tmp[0]=(byte)(val >>> 24);
    tmp[1]=(byte)(val >>> 16);
    tmp[2]=(byte)(val >>> 8);
    tmp[3]=(byte)(val);
    System.arraycopy(tmp, 0, buffer, index, 4);
    index+=4;
  }

  /**
   * Puts a 64-bit number as 8 bytes into the buffer.
   */
  public void putLong(long val) {
    tmp[0]=(byte)(val >>> 56);
    tmp[1]=(byte)(val >>> 48);
    tmp[2]=(byte)(val >>> 40);
    tmp[3]=(byte)(val >>> 32);
    System.arraycopy(tmp, 0, buffer, index, 4);
    tmp[0]=(byte)(val >>> 24);
    tmp[1]=(byte)(val >>> 16);
    tmp[2]=(byte)(val >>> 8);
    tmp[3]=(byte)(val);
    System.arraycopy(tmp, 0, buffer, index+4, 4);
    index+=8;
  }

  /**
   * skips the given number of bytes.
   */
  void skip(int n) {
    index+=n;
  }

  /**
   * Puts {@code n} bytes of zero-padding into
   * the buffer.
   */
  void putPad(int n) {
    while(n>0){
      buffer[index++]=(byte)0;
      n--;
    }
  }

  /**
   * Puts a byte[] as an unsigned multiple precision integer (mpint).
   * This consists of first the number of bytes as a
   *  {@linkplain #putInt 32-bit integer}, then the bytes of the number
   * themselves.
   */
  public void putMPInt(byte[] foo){
    // wouldn't this give an ArrayIndexOutOfBoundsException
    // for 0 = new byte[]{}?   -- P.E.
    int i=foo.length;
    if((foo[0]&0x80)!=0){
      i++;
      putInt(i);
      putByte((byte)0);
    }
    else{
      putInt(i);
    }
    putByte(foo);
  }

  /**
   * calculates the length of this buffer, which is
   * the difference between index and offset.
   */
  public int getLength(){
    return index-s;
  }

  /**
   * returns the current offset.
   */
  public int getOffSet(){
    return s;
  }
  /**
   * changes the current offset.
   * This is normally only used to start parsing a packet at a 
   * (known) later position, when creating a buffer from existing
   * data.
   * @param s the new offset.
   */
  public void setOffSet(int s){
    this.s=s;
  }

  /**
   * reads a 64-bit number from the buffer.
   * @see #putLong
   */
  public long getLong(){
    long foo = getInt()&0xffffffffL;
    foo = ((foo<<32)) | (getInt()&0xffffffffL);
    return foo;
  }

  /**
   * reads a signed 32-bit number from the buffer.
   */
  public int getInt(){
    int foo = getShort();
    foo = ((foo<<16)&0xffff0000) | (getShort()&0xffff);
    return foo;
  }

  /**
   * reads an unsigned 32-bit number from the buffer.
   */
  public long getUInt(){
    long foo = 0L;
    long bar = 0L;
    foo = getByte();
    foo = ((foo<<8)&0xff00)|(getByte()&0xff);
    bar = getByte();
    bar = ((bar<<8)&0xff00)|(getByte()&0xff);
    foo = ((foo<<16)&0xffff0000) | (bar&0xffff);
    return foo;
  }

  /**
   * reads a (unsigned) 16-bit number from the buffer.
   */
  int getShort() {
    int foo = getByte();
    foo = ((foo<<8)&0xff00)|(getByte()&0xff);
    return foo;
  }

  /**
   * reads a (unsigned) byte from the buffer.
   */
  public int getByte() {
    return (buffer[s++]&0xff);
  }

  /**
   * reads some byte from the buffer.
   * @param foo the array to put the bytes to. This array will
   *   be filled, i.e. we read foo.length bytes from the buffer.
   */
  public void getByte(byte[] foo) {
    getByte(foo, 0, foo.length);
  }

  /**
   * reads some byte from the buffer.
   * @param foo the array to put the bytes to. 
   * @param start the start index.
   * @param len the number of bytes to read.
   */
  void getByte(byte[] foo, int start, int len) {
    System.arraycopy(buffer, s, foo, start, len); 
    s+=len;
  }

  /**
   * ignores {@code len} bytes in the buffer.
   * This simply sets the offset forward by {@code len} bytes.
   *<p>
   * This is now only used in {@link #getString(int[],int[])}, and
   *   could maybe be non-public. Or at least be named something else.
   *   {@code skip} would be a nice name, but this is already used
   *   for {@linkplain #skip the same method for {@code index}}.
   *</p>
   * @return the old offset.
   */
  public int getByte(int len) {
    int foo=s;
    s+=len;
    return foo;
  }

  /**
   * reads a multiple-precision (signed) integer.
   * @see #putMPInt
   */
  public byte[] getMPInt() {
    int i=getInt();  // uint32
    if(i<0 ||  // bigger than 0x7fffffff
       i>8*1024){
      // TODO: an exception should be thrown.
      i = 8*1024; // the session will be broken, but working around OOME.
    }
    byte[] foo=new byte[i];
    getByte(foo, 0, i);
    return foo;
  }

  /**
   * reads a multiple precision signed integer
   * as unsigned (i.e. the highest-order bit is
   * guaranteed to be 0).
   */
  public byte[] getMPIntBits() {
    int bits=getInt();
    int bytes=(bits+7)/8;
    byte[] foo=new byte[bytes];
    getByte(foo, 0, bytes);
    if((foo[0]&0x80)!=0){
      byte[] bar=new byte[foo.length+1];
      bar[0]=0; // ??
      System.arraycopy(foo, 0, bar, 1, foo.length);
      foo=bar;
    }
    return foo;
  }

  /**
   * reads a string.
   * @see #putString
   * @return a new byte[] with the contents of the string.
   */
  public byte[] getString() {
    int i = getInt();  // uint32
    if(i<0 ||  // bigger than 0x7fffffff
       i>256*1024){
      // TODO: an exception should be thrown.
      i = 256*1024; // the session will be broken, but working around OOME.
    }
    byte[] foo=new byte[i];
    getByte(foo, 0, i);
    return foo;
  }

  /**
   * reads a string.
   * 
   * @param start a length-1 array where we will put the start of the data
   *   in the returned array.
   * @param len a length-1 array where we will put the length of the data
   *   in the returned array.
   * @return an array containing the data in the indexes
   *   {@code start[0]} to {@code start[0]+len[0]}. This array is in fact
   *   the internal buffer, thus it should not modified.
   */
  byte[] getString(int[]start, int[]len) {
    int i=getInt();
    start[0]=getByte(i);
    len[0]=i;
    return buffer;
  }

  /**
   * resets index and offset.
   */
  public void reset(){
    index=0;
    s=0;
  }

  /**
   * Shifts the contents between offset and index
   * back to the start of the buffer, and then sets
   * these two values accordingly.
   */
  public void shift(){
    if(s==0)return;
    System.arraycopy(buffer, s, buffer, 0, index-s);
    index=index-s;
    s=0;
  }

  /**
   * sets the offset to 0, so the same data can be read again.
   */
  void rewind(){
    s=0;
  }

  /**
   * returns the command, i.e. the byte identifying the type
   * of an SSH packet. This is the first byte of the payload,
   * i.e. the byte with index 5.
   * @see <a href="http://tools.ietf.org/html/rfc4253#section-6">RFC 4253,
   *   6. Binary Packet Protocol</a>
   */
  byte getCommand(){
    return buffer[5];
  }


  /**
   * Ensures that at least {@code n} more bytes can be
   * put into the buffer.
   *<p>
   * <em>Note:</em> This is almost never called, most commands
   *   simply assume that there is enough space.
   * </p>
   */
  void checkFreeSize(int n){
    if(buffer.length<index+n){
      byte[] tmp = new byte[buffer.length*2];
      System.arraycopy(buffer, 0, tmp, 0, index);
      buffer = tmp;
    }
  }

/*
  static String[] chars={
    "0","1","2","3","4","5","6","7","8","9", "a","b","c","d","e","f"
  };
  static void dump_buffer(){
    int foo;
    for(int i=0; i<tmp_buffer_index; i++){
        foo=tmp_buffer[i]&0xff;
	System.err.print(chars[(foo>>>4)&0xf]);
	System.err.print(chars[foo&0xf]);
        if(i%16==15){
          System.err.println("");
	  continue;
	}
        if(i>0 && i%2==1){
          System.err.print(" ");
	}
    }
    System.err.println("");
  }
  static void dump(byte[] b){
    dump(b, 0, b.length);
  }
  static void dump(byte[] b, int s, int l){
    for(int i=s; i<s+l; i++){
      System.err.print(Integer.toHexString(b[i]&0xff)+":");
    }
    System.err.println("");
  }
*/

}
